package com.finance.cooperate.dao.support.jpa;

import com.finance.cooperate.dao.support.utils.MethodUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

/**
 * @ClassName CooperateConverter
 * @Description 自定义对象转换器
 * @Author shen
 * @Date 2022/5/25 16:11
 * @Modify ...
 */
@Slf4j
public class CooperateConverter implements GenericConverter {
    private final Map<String, CacheMethod> methodCache = new ConcurrentReferenceHashMap<>(2 >> 6);
    private final CacheMethod NO_MATCH = new CacheMethod(null);

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        ConvertiblePair pair = new ConvertiblePair(Map.class, CooperateConvertible.class);
        Set<ConvertiblePair> set = new HashSet<>();
        set.add(pair);
        return set;
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        try {
            log.debug("***begin convert {} to {}", sourceType, targetType);
            Map map = (Map) source;
            Object o = targetType.getType().newInstance();
            for (Object fieldName : map.keySet()) {
                String name = getMethodName(fieldName.toString());
                String mName = o.getClass().getName().concat(".").concat(name);
                CacheMethod cacheMethod = methodCache.get(mName);
                if (cacheMethod == null) {
                    //FIXME  如果存在方法重载 ，可能会存在异常
                    Method method = Stream.of(MethodUtil.getPublicMethods(targetType.getType()))
                            .filter(m -> m.getName().equalsIgnoreCase(name))
                            .findFirst().orElse(null);
                    cacheMethod = method == null ? NO_MATCH : new CacheMethod(method);
                    methodCache.put(mName, cacheMethod);
                }
                if (cacheMethod != NO_MATCH) {
                    try {
                        Method method = cacheMethod.get();
                        method.invoke(o, caseParam(map.get(fieldName), method.getParameterTypes()[0]));
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
            }
            return o;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

    private Object caseParam(Object param, Class paramType) {
        if (param instanceof BigInteger) {
            if (param.getClass() == paramType) {
                return param;
            }
            if (paramType == Integer.class) {
                return ((BigInteger) param).intValue();
            }
            if (paramType == Long.class) {
                return ((BigInteger) param).longValue();
            }
            return param;
        }
        return param;
    }


    private String getMethodName(String fieldName) {
        String[] s = fieldName.trim().split("_");
        if (s.length > 1) {
            String m = Stream.of(s).reduce((a, b) -> firstToUp(a) + firstToUp(b)).get();
            return "set".concat(m);
        }
        return "set".concat(firstToUp(s[0]));
    }

    private String firstToUp(String s) {
        if (s.length() > 1) {
            return s.substring(0, 1).toUpperCase().concat(s.substring(1));
        }
        return s.toUpperCase();
    }

    class CacheMethod {
        private Method method;

        CacheMethod(Method method) {
            this.method = method;
        }

        Method get() {
            return this.method;
        }
    }
}
